/*!
\file
	CppFormatter.cpp
\brief
	Definition of CppFormater.
\author
	Copyright (C) 2008 Vadym Hulakov, mailto:hulakov@gmail.com.
	All rights reserved.
*/

#include "CppFormatter.h"
#include <sstream>
#include <set>
#include <algorithm>
#include <iterator>
#include <string>
#include <ctime>

namespace ObjectModel
{
	CppFormatter::CppFormatter(TypeHierarchy& manager)
							 : Formatter(manager)
	{
		_maxLineLength = 76;
	}

	void CppFormatter::build()
	{
		// for each data type
		for (TypeHierarchy::const_iterator i = _types.begin(); i != _types.end(); ++i)
		{
			Type& type = *i->second;
			if (type.isBasic())
				continue;
			to_cpp(type, true);
			to_cpp(type, false);
		}
	}

	void CppFormatter::to_cpp(const Type& data_type, bool declaration)
	{
		std::wstring filename;
		generate_filename(data_type, declaration, filename);
		std::wstringstream code;
		to_cpp(data_type, code, declaration, 0);

		_results[filename] = code.str();
	}

	void CppFormatter::to_cpp_data_type(const Type& data_type, std::wstring& name)
	{
		name = data_type.getName();
	}

	const std::wstring& replicateTabs(int count)
	{
		static std::map<int, std::wstring> res;
		if (res.find(count) == res.end())
		{
			std::wstring str;
			int i = count;
			while (i-- > 0)
				str += L"\t";
			res[count] = str;
		}
		return res[count];
	}

	bool break_line(const std::wstring& line, std::wstring& result,
		int offset, const std::wstring& separator, int maxLineLength)
	{
		const wchar_t* pos = line.c_str();
		const wchar_t* end = pos + line.size();
		std::wstring::size_type cur_max_length = maxLineLength - 
			(separator.length() - 
			separator.find_last_of('\n') - 1) - offset * 4;
		const std::wstring& tabs = replicateTabs(offset);
		while (pos + cur_max_length < end)
		{
			const wchar_t* new_pos = pos + cur_max_length + 1;
			while (iswspace(*--new_pos) == 0);
			new_pos++;
			result.append(pos, new_pos - pos);
			result += L'\n' + tabs + separator;
			pos = new_pos;
		}
		result += pos;
		return true;
	}

	const std::wstring& spaces(int count)
	{
		static std::map<int, std::wstring> res;
		if (res.find(count) == res.end())
		{
			std::wstring str;
			int i = count;
			while (i-- > 0)
				str += L" ";
			res[count] = str;
		}
		return res[count];
	}

	bool CppFormatter::format_comment(std::wstring& comment, 
		int offset_before, int offset_after)
	{
		std::wstring prefix = L"//" + spaces(offset_after + 1);
		std::wstring multyline_comment;
		break_line(comment, multyline_comment,
			offset_before, prefix, _maxLineLength);
		comment = replicateTabs(offset_before) + prefix + multyline_comment;
		return true;
	}

	bool CppFormatter::to_cpp(const Type& data_type, const Attribute& info,
		std::wostream &stream, bool declaration, int offset)
	{
		if (!declaration)
		{
			return true;
		}

		std::wstring type;
		to_cpp_data_type(info.getType(), type);
		std::wstring comment = info.getDescription();
		format_comment(comment, offset, 0);

		stream << comment << std::endl << replicateTabs(offset) << type << L' ';
		if (!declaration)
		{
			// !!!
		}
		stream << info.getName() + L';' << std::endl;
		return true;
	}

	bool CppFormatter::to_cpp(const Type& data_type, 
		const Parameter& info, std::wostream &stream)
	{
		std::wstring type;
		to_cpp_data_type(info.getType(), type);
		stream << type << L' ' << info.getName();
		return true;
	}

	bool CppFormatter::to_cpp(const Type& data_type, const Method& info,
		std::wostream &stream, bool declaration, int offset)
	{
		if (info.isPure() && !declaration)
			return true;

		std::wstring type;
		to_cpp_data_type(info.getReturnType(), type);

		std::wstringstream params_decl;
		const Method::Parameters& params = info.getParameters();
		for (Method::Parameters::const_iterator param = params.begin();
			param != params.end(); ++param)
		{
			if (param != params.begin())
				params_decl << L", ";
			to_cpp(data_type, *param, params_decl);
		}

		if (declaration)
		{
			std::wstring comment = info.getDescription();
			format_comment(comment, offset, 0);

			std::wstringstream param_comments;
			for (Method::Parameters::const_iterator param = params.begin();
				param != params.end(); ++param)
			{
				std::wstring param_comment_name = param->getName() + L":";
				format_comment(param_comment_name, offset, 2);
				std::wstring param_comment = param->getDescription();
				format_comment(param_comment, offset, 5);
				param_comments << std::endl <<
					param_comment_name << std::endl << param_comment;
			}

			stream << comment << std::endl;
			if (params.empty())
			{
				std::wstring title = L"Parameters:";
				format_comment(title, offset, 0);
				stream << title << param_comments.str();
				stream << std::endl;
			}
			
			stream << replicateTabs(offset);
			if (info.isPure())
				stream << "virtual ";
			stream << type << L' ';
			stream << info.getName() << L'(' << params_decl.str() << L")";
			if (info.isPure())
				stream << " = 0";
			stream << L";";
			stream << std::endl;
		}
		else
		{
			stream << replicateTabs(offset) << type << L' ' <<
				data_type.getName() << L"::" << info.getName() <<
				L'(' << params_decl.str() << L")" 
				<< L"\n{\n}\n" << std::endl;
		}
		return true;
	}

	const wchar_t* CppFormatter::to_cpp(enum ClassMemberAccess access) const
	{
		switch (access)
		{
		case ClassMemberAccessPublic:
			return L"public";
		case ClassMemberAccessProtected:
			return L"protected";
		case ClassMemberAccessPrivate:
			return L"private";
		default:
			return L"";
		}
	}

	void CppFormatter::print_member_access(std::wostream &stream,
		ClassMemberAccess access)
	{
		stream << to_cpp(access) << L":\n";
	}

	bool CppFormatter::to_cpp(const Type& data_type, std::wostream &stream,
		bool declaration, int offset)
	{
		const ClassType *type = dynamic_cast<const ClassType*>(&data_type);
		if (type != 0)
		{
			generate_header(data_type, stream, declaration);
			generate_includes(*type, stream, declaration);
			if (declaration)
			{
				std::wstring comment = type->getDescription();
				format_comment(comment, 0, 0);
				stream << comment << std::endl 
					<<  "class" << L' ' << type->getName();
				const ClassType* info = type->basicClass();
				if (info)
					stream << " : " << to_cpp(ClassMemberAccessPublic) <<
						" " << info->getName();
				stream << std::endl << L"{" << std::endl;
			}

			for (int i = 0; i < 4; ++i)
			{
				ClassMemberAccess access = (ClassMemberAccess)i;
				bool is_first = true;
				const ClassType::Attributes& attributes = type->getAttributes();
				for (ClassType::Attributes::const_iterator attribute = 
					attributes.begin(); attribute != attributes.end();
					++attribute)
				{
					if (attribute->getAccess() == access)
					{
						if (is_first && declaration)
							print_member_access(stream, access), is_first = false;
						to_cpp(data_type, *attribute, stream, declaration, offset + 1);
					}
				}

				const ClassType::Methods& methods = type->getMethods();
				for (ClassType::Methods::const_iterator method = 
					methods.begin(); method != methods.end(); ++method)
				{
					if (method->getAccess() == access)
					{
						if (is_first && declaration)
							print_member_access(stream, access),
							is_first = false;
						to_cpp(data_type, *method, stream, 
							declaration, offset + (declaration ? 1 : 0));
					}
				}
			}

			if (declaration)
			{
				stream << L"};";
			}
		}
		return true;
	}

	void CppFormatter::generate_filename(const Type& data_type,
		bool declaration, std::wstring &filename)
	{
		filename = data_type.getName() + L'.' +
			(declaration ? L"h" : L"cpp");
	}

	bool CppFormatter::generate_includes(const ClassType& data_type, 
		std::wostream &stream, bool declaration)
	{
		typedef std::set<const Type*> DataTypeInfoSet;
		DataTypeInfoSet includes;
		if (!declaration)
			includes.insert(&data_type);

		if (declaration)
		{
			if (data_type.basicClass())
			{
				includes.insert(data_type.basicClass());
			}

			const ClassType::Attributes& attributes = 
				data_type.getAttributes();
			for (ClassType::Attributes::const_iterator attribute =
				attributes.begin(); 
				attribute != attributes.end(); ++attribute)
			{
				const Type& type = attribute->getType();
				if (!type.isBasic())
					includes.insert(&type);
			}

			const ClassType::Methods& methods = data_type.getMethods();
			for (ClassType::Methods::const_iterator method = 
				methods.begin(); method != methods.end(); ++method)
			{
				const Type& ret_type = method->getReturnType();
				if (!ret_type.isBasic())
					includes.insert(&ret_type);

				const Method::Parameters& parameters = method->getParameters();
				for(Method::Parameters::const_iterator param = 
					parameters.begin(); param != parameters.end(); ++param)
				{
					const Type& type = param->getType();
					if (!type.isBasic())
						includes.insert(&type);
				}
			}
		}

		if (!includes.empty())
		{
			for (DataTypeInfoSet::const_iterator i =
				includes.begin(); i != includes.end(); ++i)
			{
				std::wstring filename;
				generate_filename(**i, true, filename);
				stream << L"#include \"" << filename << '"' << std::endl;
			}
			stream << std::endl;
		}

		return true;
	}

	void date_time(std::wstring &date)
	{
		time_t calender_time = time(0);
		tm today;
#ifdef WIN32
		localtime_s(&today, &calender_time);
#else // WIN32
		today = *localtime(&calender_time);
#endif
		const size_t buf_size = 128;
		//__time64_t timer;
		//_time64(&timer);
		//tm today;
		//_localtime64_s(&today, &timer);
		wchar_t tmpbuf[buf_size]; 
		wcsftime(tmpbuf, buf_size, L"%#c", &today);
		date = tmpbuf;
	}

	bool CppFormatter::generate_header(const Type& data_type, 
		std::wostream &stream, bool declaration)
	{
		// Declare vector on header elements. 
		// For each such element will be generated line in header in format:
		// [NAME]: [VALUE]
		typedef std::pair<const wchar_t*, const wchar_t*> InfoType;
		std::vector<InfoType> lines;

		// Filename
		std::wstring filename;
		generate_filename(data_type, declaration, filename);
		lines.push_back(InfoType(L"File", filename.c_str()));

		// description
		std::wstring description = 
			(declaration ? L"Declaration of " : L"Definition of ") + 
			data_type.getName();
		lines.push_back(InfoType(L"Description", description.c_str()));
		
		// bugs and todo
		std::wstring none = L"-";
		lines.push_back(InfoType(L"Bugs", none.c_str()));
		lines.push_back(InfoType(L"Todo", none.c_str()));

		// creation date
		std::wstring date_created;
		date_time(date_created);
		lines.push_back(InfoType(L"Creation date", date_created.c_str()));
	
		// author, license and copyrights 
		lines.push_back(InfoType(L"Author", _author.c_str()));
		lines.push_back(InfoType(L"License", _license.c_str()));
		lines.push_back(InfoType((const wchar_t*)0, _copyright.c_str()));

		// generate separator
		std::wstring separator;
		separator.resize(_maxLineLength);
		std::fill(separator.begin(), separator.end(), L'/');

		// generation start
		stream << separator << std::endl;
	
		const wchar_t* namePrefix = L"// ";
		int namePrefixLen = wcslen(namePrefix);
		const wchar_t* nameSuffix = L":";
		int nameSuffixLen = wcslen(nameSuffix);

		for (std::vector<InfoType>::const_iterator i = lines.begin(); 
			i != lines.end(); ++i)
		{
			const int tabSize = 4;
			const int namesLength = tabSize * 5;
		
			const wchar_t* name = i->first;
			const wchar_t* value = i->second;
		
			// put to output stream prefix (e.g. "// ")
			stream << namePrefix;

			// if we have name
			if (name)
			{
				
				// put to output stream name end suffix (e.g. "foo name:")
				stream << name;
				stream << nameSuffix;

				// put to output stream tabs after name to make values
				// aligned by left side
				size_t nameLen = wcslen(name);
				int tabsLength = namesLength - nameLen - 
					namePrefixLen - nameSuffixLen;
				int tabsCount = tabsLength / tabSize + 
					((tabsLength % tabSize) ? 1 : 0);
				stream << std::wstring(tabsCount, L'\t').c_str();
			}
			// put to output stream value
			stream << value << std::endl;
		}
	
		stream << separator << std::endl << std::endl;
		return true;
	}
}
